/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package game.jetclan.main.marcustestar;

import com.jme3.animation.AnimChannel;
import com.jme3.animation.AnimControl;
import com.jme3.app.SimpleApplication;
import com.jme3.asset.TextureKey;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.collision.shapes.CollisionShape;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.bullet.util.CollisionShapeFactory;
import com.jme3.collision.CollisionResult;
import com.jme3.collision.CollisionResults;
import com.jme3.font.BitmapText;
import com.jme3.input.ChaseCamera;
import com.jme3.input.KeyInput;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.AnalogListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.input.controls.MouseButtonTrigger;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Ray;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.renderer.queue.RenderQueue.ShadowMode;
import com.jme3.scene.Geometry;
import com.jme3.scene.shape.Box;
import com.jme3.scene.shape.Cylinder;
import com.jme3.scene.shape.Sphere;
import com.jme3.scene.shape.Sphere.TextureMode;
import com.jme3.shadow.BasicShadowRenderer;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture.WrapMode;
import com.jme3.scene.Node;
import com.jme3.terrain.geomipmap.TerrainLodControl;
import com.jme3.terrain.geomipmap.TerrainQuad;
import com.jme3.terrain.heightmap.AbstractHeightMap;
import com.jme3.terrain.heightmap.ImageBasedHeightMap;
import com.jme3.texture.Image.Format;

/**
 * Example 12 - how to give objects physical properties so they bounce and fall.
 *
 * @author base code by double1984, updated by zathras
 */
public class KubbClient extends SimpleApplication {

    public static void main(String args[]) {
        KubbClient app = new KubbClient();
        app.start();
    }
    private AnimChannel channel;
    private AnimControl control;
    //static Kubb myKubb = null;
    private static final Cylinder cylinder;
    private RigidBodyControl kubbPhysics;
    Geometry geometryKubb;
    /**
     * Prepare the Physics Application State (jBullet)
     */
    private BulletAppState bulletAppState;
    /**
     * Activate custom rendering of shadows
     */
    BasicShadowRenderer bsr;
    //Text to be displayed on the screen
    protected BitmapText hintText;
    /**
     * Prepare Materials
     */
    Material wall_mat;
    Material stone_mat;
    Material floor_mat;
    Material materialKubb;
    /**
     * Prepare geometries and physical nodes for bricks and cannon balls.
     */
    private RigidBodyControl brick_phy;
    private static final Box box;
    private RigidBodyControl ball_phy;
    private static final Sphere sphereSmall;
    private static final Sphere sphereBig;
    private RigidBodyControl floor_phy;
    private static final Box floor;
    /**
     * dimensions used for bricks and wall
     */
    private static final float brickLength = 0.48f;
    private static final float brickWidth = 0.24f;
    private static final float brickHeight = 0.12f;
    long mPressStartTime = 0;
    int mLastVelocity = 0;
    static float mSurfaceWidth = 130f;
    static float mSurfaceDepth = 130f;
    static float mSurfaceThickness = 1.0f;
    //SpaceShip spaceShip = null;
    Node mCamNode = null;//Node to attach cam to
    ChaseCamera mChaseCam;
    //CharacterControl characterControl = new CharacterControl();
    //RigidBodyControl characterControl;
    //Node myNode = new Node();
    Vector3f walkDirection = new Vector3f();
    //Clean
    SpaceShip playerSpaceShip = null;
    SpaceShip playerSpaceShipSmall = null;
    Cannon playerCannon = null;
    PlayableObject currentPlayerObject = null;
    //Shooting with mouse
    Node shootables;
    Geometry mark;
    Geometry aimLine;
    private TerrainQuad terrain;
    Material mat_terrain;

    static {
        /**
         * Initialize the cannon ball geometry
         */
        sphereSmall = new Sphere(32, 32, 0.2f, true, false);
        sphereSmall.setTextureMode(TextureMode.Projected);
        sphereBig = new Sphere(32, 32, 0.4f, true, false);
        sphereBig.setTextureMode(TextureMode.Projected);
        /**
         * Initialize the brick geometry
         */
        box = new Box(Vector3f.ZERO, brickLength, brickHeight, brickWidth);
        box.scaleTextureCoordinates(new Vector2f(1f, 1f));
        /**
         * Initialize the floor geometry
         */
        floor = new Box(Vector3f.ZERO, mSurfaceWidth, mSurfaceThickness, mSurfaceDepth);
        floor.scaleTextureCoordinates(new Vector2f(3, 6));

        cylinder = new Cylinder(32, 32, 1, 2, true);
        //cylinder = new Box(Vector3f.ZERO, 3f, 1f, 2f);
        //cylinder.scaleTextureCoordinates(new Vector2f(1f, 0.5f));

    }

    @Override
    public void simpleInitApp() {
        /**
         * Set up Physics Game
         */
        bulletAppState = new BulletAppState();
        stateManager.attach(bulletAppState);
        //bulletAppState.getPhysicsSpace().enableDebug(assetManager);
        /**
         * Configure cam to look at scene
         */
        flyCam.setEnabled(false);

        DirectionalLight sun = new DirectionalLight();
        sun.setDirection(new Vector3f(-0.1f, -0.7f, -1.0f));
        rootNode.addLight(sun);
        initHeightMap();
        /**
         * Add InputManager action: Left click triggers shooting.
         */
        initInputs();
        initMark();
        /**
         * Initialize the scene, materials, and physics space
         */
        initMaterials();
        //initWall();
        //initKubbField();
        //System.out.println("4: myKubb translation: " + myKubb.getGeometry().getLocalTranslation());
        initFloor();
        //System.out.println("5: myKubb translation: " + myKubb.getGeometry().getLocalTranslation());
//        initCrossHairs();
        //System.out.println("6: myKubb translation: " + myKubb.getGeometry().getLocalTranslation());
        initShadows();
        //System.out.println("7: myKubb translation: " + myKubb.getGeometry().getLocalTranslation());
        loadHintText();
        initShootables();

        mCamNode = new Node();
        mChaseCam = new ChaseCamera(cam, mCamNode, inputManager);
        mChaseCam.setSmoothMotion(true);

        playerSpaceShip = new SpaceShip(100f, bulletAppState, assetManager);
        playerSpaceShip.setLocalTranslation(new Vector3f(30, 2, -15));
        playerSpaceShip.attachCam(mCamNode);
        rootNode.attachChild(playerSpaceShip.getMainNode());

        playerSpaceShipSmall = new SpaceShip(1f, bulletAppState, assetManager);
        playerSpaceShipSmall.setLocalTranslation(new Vector3f(0, 0, 0));
        playerSpaceShipSmall.getMainNode().scale(0.5f, 0.5f, 0.5f);
        playerSpaceShipSmall.setCharacteristics(50f, 25f, 10f, 7f, 10f, 5f, 5f);
        rootNode.attachChild(playerSpaceShipSmall.getMainNode());


        setCurrentUserObject(playerSpaceShip);
        //control = house.getControl(AnimControl.class);         
        //channel = control.createChannel();          
        //channel.setAnim("my_animation"); 

    }

    private int getVelocity(long startTime) {
        if (startTime == 0) {
            return 0;
        }

        float totalPressTime = System.currentTimeMillis() - startTime;
        float velocity = totalPressTime / 100f;
        velocity = Math.max(10f, velocity);
        velocity = Math.min(25, velocity);

        return (int) velocity;
    }
    /**
     * Every time the shoot action is triggered, a new cannon ball is produced.
     * The ball is set up to fly from the camera position in the camera
     * direction.
     */
    private AnalogListener shipActionListener = new AnalogListener() {
        //float releaseTime = 0;
        public void onAnalog(String name, float value, float tpf) {
            currentPlayerObject.consumeAnalogAction(name, value, tpf);
        }
    };
    private ActionListener shootActionListener = new ActionListener() {
        //float releaseTime = 0;
        public void onAction(String name, boolean isKeyPressed, float tpf) {
            inputManager.getCursorPosition();
            currentPlayerObject.consumeAction(name, isKeyPressed, tpf);

            if (name.equals("shootRight") && !isKeyPressed) {
                // 1. Reset results list.
                CollisionResults results = new CollisionResults();
                // 2. Aim the ray from cam loc to cam direction.
                Ray ray = new Ray(currentPlayerObject.getMainNode().getLocalTranslation(), currentPlayerObject.getMainNode().getLocalRotation().getRotationColumn(2));
                // 3. Collect intersections between Ray and Shootables in results list.
                shootables.collideWith(ray, results);
                // 4. Print the results
                System.out.println("----- Collisions? " + results.size() + "-----");
                for (int i = 0; i < results.size(); i++) {
                    // For each hit, we know distance, impact point, name of geometry.
                    float dist = results.getCollision(i).getDistance();
                    Vector3f pt = results.getCollision(i).getContactPoint();
                    String hit = results.getCollision(i).getGeometry().getName();
                    System.out.println("* Collision #" + i);
                    System.out.println("  You shot " + hit + " at " + pt + ", " + dist + " wu away.");
                }
                // 5. Use the results (we mark the hit object)
                if (results.size() > 0) {
                    // The closest collision point is what was truly hit:
                    CollisionResult closest = results.getClosestCollision();
                    // Let's interact - we mark the hit with a red dot.
                    mark.setLocalTranslation(closest.getContactPoint());
                    rootNode.attachChild(mark);
                } else {
                    // No hits? Then remove the red mark.
                    rootNode.detachChild(mark);
                }

            }
            if (name.equals("ShowShip1")) {
                setCurrentUserObject(playerSpaceShip);
            } else if (name.equals("ShowShip2")) {
                setCurrentUserObject(playerSpaceShipSmall);
            }
        }
    };

    /**
     * Initialize the materials used in this scene.
     */
    public void initMaterials() {
        wall_mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        TextureKey key = new TextureKey("Textures/road.png");
        key.setGenerateMips(true);
        Texture tex = assetManager.loadTexture(key);
        wall_mat.setTexture("ColorMap", tex);
        stone_mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        TextureKey key2 = new TextureKey("Textures/dirt.png");
        key2.setGenerateMips(true);
        Texture tex2 = assetManager.loadTexture(key2);
        stone_mat.setTexture("ColorMap", tex2);
        floor_mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        TextureKey key3 = new TextureKey("Textures/grass.png");
        key3.setGenerateMips(true);
        Texture tex3 = assetManager.loadTexture(key3);
        tex3.setWrap(WrapMode.Repeat);
        floor_mat.setTexture("ColorMap", tex3);


        materialKubb = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        TextureKey keyKubb = new TextureKey("Textures/road.png");
        key.setGenerateMips(true);
        Texture texture = assetManager.loadTexture(key);
        materialKubb.setTexture("ColorMap", texture);
    }

    /**
     * Make a solid floor and add it to the scene.
     */
    public void initFloor() {
        Geometry floor_geo = new Geometry("Floor", floor);
        floor_geo.setMaterial(floor_mat);
        floor_geo.setShadowMode(ShadowMode.Receive);
        floor_geo.setLocalTranslation(0, -mSurfaceThickness, 0);
        rootNode.attachChild(floor_geo);
        /* Make the floor physical with mass 0.0f! */
        floor_phy = new RigidBodyControl(0.0f);

        floor_geo.addControl(floor_phy);
        bulletAppState.getPhysicsSpace().add(floor_phy);
        floor_phy.setFriction(3f);
    }

    /**
     * This loop builds a wall out of individual bricks.
     */
    public void initWall() {
        float startpt = brickLength / 4;
        float height = 0;
        Box wall = new Box(Vector3f.ZERO, mSurfaceWidth / 2, 10f, 0.1f);
        Geometry geo = new Geometry("BigWall", wall);
        geo.setLocalTranslation(-mSurfaceWidth / 2, 0, -mSurfaceDepth);
        geo.setMaterial(stone_mat);
        rootNode.attachChild(geo);
        RigidBodyControl wallPhys = new RigidBodyControl(0.0f);
        geo.addControl(wallPhys);
        bulletAppState.getPhysicsSpace().add(wallPhys);

    }

    /**
     * Activate shadow casting and light direction
     */
    private void initShadows() {
        bsr = new BasicShadowRenderer(assetManager, 256);
        bsr.setDirection(new Vector3f(-1, -1, -1).normalizeLocal());
        viewPort.addProcessor(bsr);
        // Default mode is Off -- Every node declares own shadow mode!
        rootNode.setShadowMode(ShadowMode.Off);
    }

    /**
     * This method creates one individual physical cannon ball. By defaul, the
     * ball is accelerated and flies from the camera position in the camera
     * direction.
     */
    public void makeCannonBall(float aMass, Box aSphere, int acceleration) {
        /**
         * Create a cannon ball geometry and attach to scene graph.
         */
        Geometry ball_geo = new Geometry("cannon ball", aSphere);
        ball_geo.setMaterial(stone_mat);
        rootNode.attachChild(ball_geo);
        /**
         * Position the cannon ball and activate shadows
         */
        ball_geo.setLocalTranslation(currentPlayerObject.getMainNode().getLocalTranslation().getX() - 10, currentPlayerObject.getMainNode().getLocalTranslation().getY(), currentPlayerObject.getMainNode().getLocalTranslation().getZ());
        ball_geo.setShadowMode(ShadowMode.CastAndReceive);
        /**
         * Make the ball physcial with a mass > 0.0f
         */
        ball_phy = new RigidBodyControl(aMass);
        //ball_phy.set
        /**
         * Add physical ball to physics space.
         */
        ball_geo.addControl(ball_phy);
        bulletAppState.getPhysicsSpace().add(ball_phy);
        //ball_phy.setFriction(10f);
        /**
         * Accelerate the physcial ball to shoot it.
         */
        ball_phy.setLinearVelocity(currentPlayerObject.getMainNode().getLocalRotation().getRotationColumn(0).mult(acceleration * 10));
    }

    /**
     * A plus sign used as crosshairs to help the player with aiming.
     */
    protected void initCrossHairs() {
        guiNode.detachAllChildren();
        guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
        BitmapText ch = new BitmapText(guiFont, false);
        ch.setSize(guiFont.getCharSet().getRenderedSize() * 2);
        ch.setText("+");        // fake crosshairs :)
        ch.setLocalTranslation( // center
                settings.getWidth() / 2 - guiFont.getCharSet().getRenderedSize() / 3 * 2,
                settings.getHeight() / 2 + ch.getLineHeight() / 2, 0);
        guiNode.attachChild(ch);
    }

    public void loadHintText() {
        hintText = new BitmapText(guiFont, false);
        hintText.setSize(guiFont.getCharSet().getRenderedSize());
        hintText.setLocalTranslation(0, getCamera().getHeight(), 0);

        guiNode.attachChild(hintText);
    }

//    @Override
    public void simpleUpdate(float tpf) {
        // System.out.println("CurrentTime: " + System.currentTimeMillis());
        super.simpleUpdate(tpf);
        String text = "Velocity = " + getVelocity(mPressStartTime) + "\n" + "Last velocity = " + mLastVelocity;
        hintText.setText(text);
        currentPlayerObject.update(tpf);
        //SceneUtil.checkKubbs();
//        if(myKubb != null)
//        {
//            myKubb.rotateTEST(tpf*2,0,0);
//        }
    }

    private void initInputs() {
        inputManager.addMapping("shootLeft", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
        inputManager.addMapping("shootRight", new MouseButtonTrigger(MouseInput.BUTTON_RIGHT));
        inputManager.addMapping("Lefts", new KeyTrigger(KeyInput.KEY_A));
        inputManager.addMapping("Rights", new KeyTrigger(KeyInput.KEY_D));
        inputManager.addMapping("Forward", new KeyTrigger(KeyInput.KEY_W));
        inputManager.addMapping("Backward", new KeyTrigger(KeyInput.KEY_S));
        inputManager.addMapping("Brake", new KeyTrigger(KeyInput.KEY_SPACE));
        inputManager.addMapping("BigFrustum", new KeyTrigger(KeyInput.KEY_B));
        inputManager.addMapping("SmallFrustum", new KeyTrigger(KeyInput.KEY_N));
        inputManager.addMapping("ShowOrHideAimLine", new KeyTrigger(KeyInput.KEY_Y));
        inputManager.addMapping("ShowShip1", new KeyTrigger(KeyInput.KEY_1));
        inputManager.addMapping("ShowShip2", new KeyTrigger(KeyInput.KEY_2));

        inputManager.addListener(shootActionListener, "shootLeft");
        inputManager.addListener(shootActionListener, "shootRight");
        inputManager.addListener(shootActionListener, "ShowOrHideAimLine");
        inputManager.addListener(shootActionListener, "ShowShip1");
        inputManager.addListener(shootActionListener, "ShowShip2");

        inputManager.addListener(shipActionListener, "Lefts");
        inputManager.addListener(shipActionListener, "Rights");
        inputManager.addListener(shipActionListener, "Forward");
        inputManager.addListener(shipActionListener, "Backward");
        inputManager.addListener(shipActionListener, "Brake");
        inputManager.addListener(shipActionListener, "BigFrustum");
        inputManager.addListener(shipActionListener, "SmallFrustum");
    }

    private void initShootables() {
        shootables = new Node("Shootables");
        rootNode.attachChild(shootables);
        shootables.attachChild(makeCube("a Dragon", -10f, 5f, 10f));
        shootables.attachChild(makeCube("a tin can", 10f, 5f, 30f));
        shootables.attachChild(makeCube("the Sheriff", -10f, 5f, -10f));
        shootables.attachChild(makeCube("the Deputy", 10f, 5f, -25f));

    }

    /**
     * A cube object for target practice
     */
    protected Geometry makeCube(String name, float x, float y, float z) {
        Box box = new Box(new Vector3f(x, y, z), 1, 1, 1);
        Geometry cube = new Geometry(name, box);
        Material mat1 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        mat1.setColor("Color", ColorRGBA.randomColor());
        cube.setMaterial(mat1);
        return cube;
    }

    /**
     * A red ball that marks the last spot that was "hit" by the "shot".
     */
    protected void initMark() {
        Sphere sphere = new Sphere(30, 30, 0.2f);
        mark = new Geometry("BOOM!", sphere);
        Material mark_mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        mark_mat.setColor("Color", ColorRGBA.Red);
        mark.setMaterial(mark_mat);
    }

    /**
     *
     * @param aPlayableObject Object that a player want to controlled
     * @return Returns if the attended object to use is busy or not
     */
    private void setCurrentUserObject(PlayableObject aPlayableObject) {


        if (!aPlayableObject.isBusy()) {
            if (currentPlayerObject != null) {
                currentPlayerObject.detachCam(mCamNode);
                currentPlayerObject.setIsBusy(false);
            }
            currentPlayerObject = aPlayableObject;
            currentPlayerObject.attachCam(mCamNode);
            currentPlayerObject.setIsBusy(true);
        }
    }

    private void initHeightMap() {
        /**
         * 1. Create terrain material and load four textures into it.
         */
        mat_terrain = new Material(assetManager,
                "Common/MatDefs/Terrain/Terrain.j3md");

        /**
         * 1.1) Add ALPHA map (for red-blue-green coded splat textures)
         */
        mat_terrain.setTexture("Alpha", assetManager.loadTexture(
                "Textures/alphamap.png"));

        /**
         * 1.2) Add GRASS texture into the red layer (Tex1).
         */
        Texture grass = assetManager.loadTexture(
                "Textures/grass.png");
        grass.setWrap(WrapMode.Repeat);
        mat_terrain.setTexture("Tex1", grass);
        mat_terrain.setFloat("Tex1Scale", 64f);

        /**
         * 1.3) Add DIRT texture into the green layer (Tex2)
         */
        Texture dirt = assetManager.loadTexture(
                "Textures/dirt.png");
        dirt.setWrap(WrapMode.Repeat);
        mat_terrain.setTexture("Tex2", dirt);
        mat_terrain.setFloat("Tex2Scale", 32f);

        /**
         * 1.4) Add ROAD texture into the blue layer (Tex3)
         */
        Texture rock = assetManager.loadTexture(
                "Textures/road.png");
        rock.setWrap(WrapMode.Repeat);
        mat_terrain.setTexture("Tex3", rock);
        mat_terrain.setFloat("Tex3Scale", 128f);

        /**
         * 2. Create the height map
         */
        AbstractHeightMap heightmap = null;
        Texture heightMapImage = assetManager.loadTexture(
                "Textures/new_terrain/mountains512.png");
        //heightMapImage.getImage().setFormat(Format.RGB8);
        heightmap = new ImageBasedHeightMap(heightMapImage.getImage());
        heightmap.load();

        /**
         * 3. We have prepared material and heightmap. Now we create the actual
         * terrain: 3.1) Create a TerrainQuad and name it "my terrain". 3.2) A
         * good value for terrain tiles is 64x64 -- so we supply 64+1=65. 3.3)
         * We prepared a heightmap of size 512x512 -- so we supply 512+1=513.
         * 3.4) As LOD step scale we supply Vector3f(1,1,1). 3.5) We supply the
         * prepared heightmap itself.
         */
        int patchSize = 65;
        terrain = new TerrainQuad("my terrain", patchSize, 513, heightmap.getHeightMap());

        /**
         * 4. We give the terrain its material, position & scale it, and attach
         * it.
         */
        terrain.setMaterial(mat_terrain);
        terrain.setLocalTranslation(0, -500, 0);
        terrain.setLocalScale(2f, 1f, 2f);
        rootNode.attachChild(terrain);

        /**
         * 5. The LOD (level of detail) depends on were the camera is:
         */
        TerrainLodControl control = new TerrainLodControl(terrain, getCamera());
        terrain.addControl(control);

        CollisionShape terrainShape =
                CollisionShapeFactory.createMeshShape((Node) terrain);
        RigidBodyControl landscape = new RigidBodyControl(terrainShape, 0);
        terrain.addControl(landscape);
        bulletAppState.getPhysicsSpace().add(terrain);
    }
}
